package stock

import (
	"fmt"
	"strings"
	"time"

	"gitee.com/lyuanbo/stock-plus/internal/svc"
	"gitee.com/lyuanbo/stock-plus/pkg/consts"
	"gitee.com/lyuanbo/stock-plus/pkg/message"
	"gitee.com/lyuanbo/stock-plus/pkg/stock/indicators"
	storagememory "gitee.com/lyuanbo/stock-plus/pkg/stock/storage_memory"
	"gitee.com/lyuanbo/stock-plus/pkg/worker_manager"
	"github.com/bytedance/sonic"
	"github.com/nats-io/nats.go"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
)

func init() {
	workermanager.Register(func(svcCtx *svc.ServiceContext, cache *storagememory.Cache) workermanager.Tasker {
		return NewCCIWorker(svcCtx, cache)
	})
}

var _ workermanager.Tasker = (*CCIWorker)(nil)

type CCIWorker struct {
	svcCtx *svc.ServiceContext
	cache  *storagememory.Cache
}

func NewCCIWorker(svcCtx *svc.ServiceContext, cache *storagememory.Cache) *CCIWorker {
	c := &CCIWorker{
		svcCtx: svcCtx,
		cache:  cache,
	}

	return c
}

func (c *CCIWorker) Init() {
	// 获取所有数据
	cciPeriods := c.svcCtx.Conf.Get().CCI
	for _, symbol := range c.svcCtx.Stock.XueQiu.GetSymbols() {
		for _, period := range cciPeriods {
			key := c.cache.GetKey(symbol, period)
			rb := c.cache.GetRingBuffer(key)
			items := rb.SafeGetAll()
			indicators.CCI(items, 14)
		}
	}
}

func (c *CCIWorker) Name() string {
	return "cci"
}

func (c *CCIWorker) Start() {
	go c.Run()
}

func (c *CCIWorker) Run() {
	_, _ = c.svcCtx.Nats.Subscribe(consts.TopicWorkerStart, func(msg *nats.Msg) {
		var req WorkerStartReq
		err := sonic.Unmarshal(msg.Data, &req)
		if err != nil {
			c.svcCtx.Logger.Warn(c.svcCtx.Ctx, "cci worker unmarshal msg error", zap.Error(err))
			_ = msg.Nak()
			return
		}

		key := c.cache.GetKey(req.Symbol, req.Period)
		rb := c.cache.GetRingBuffer(key)

		rb.Mux.Lock()
		items := rb.GetLast(-14) // n>len(items)时，返回全部数据
		rb.Mux.Unlock()

		indicators.CCI(items, 14) // 只计算最近14根K线的CCI
		c.task60m(req.Symbol, req.Period)
	})
}

func (c *CCIWorker) task60m(symbol string, period string) {
	if period != "60m" {
		return
	}
	key := c.cache.GetKey(symbol, period)

	rb := c.cache.GetRingBuffer(key)

	rb.Mux.Lock()
	items := rb.GetLast(-100) // n>len(items)时，返回全部数据
	rb.Mux.Unlock()
	if len(items) < 14 {
		return
	}

	kline := items[len(items)-1]

	// 往上找最后一个突破上轨的K线
	var (
		high, low   decimal.Decimal
		triggerTime time.Time
		idx         int
		kind        string
	)

	for i := len(items) - 1; i > 0; i-- {
		if (items[i-1].CCI < 100 && items[i].CCI >= 100) || (items[i-1].CCI > -100 && items[i].CCI <= -100) {
			idx = i
			high = items[i].KlineInfo.High
			low = items[i].KlineInfo.Low
			triggerTime = items[i].KlineInfo.Timestamp

			break
		}
	}
	if high.IsZero() && low.IsZero() {
		return
	}

	msg := message.Message{
		Title:  fmt.Sprintf("CCI: %s", kline.StockInfo.Name),
		Url:    fmt.Sprintf("https://xueqiu.com/S/%s", strings.ToUpper(kline.StockInfo.Symbol)),
		Expire: int64(time.Until(kline.KlineInfo.Timestamp).Seconds()),
	}

	if c.svcCtx.Stock.XueQiu.GetStockInfo(kline.StockInfo.Symbol).IsHeld {
		msg.Level = message.LevelCritical
	}

	if kline.KlineInfo.Close.GreaterThan(high) {
		var isSend = true
		for i := len(items) - 2; i > idx; i-- {
			if items[i].KlineInfo.High.GreaterThan(high) {
				isSend = false
				continue
			}
		}

		if isSend {
			kind = "up"
			msg.Subtitle = fmt.Sprintf("当前: %s, 突破%s高点", kline.KlineInfo.Close.String(), triggerTime.Format("01-02 15:04"))
		}
	}

	if kline.KlineInfo.Close.LessThan(low) {
		var isSend = true
		for i := len(items) - 2; i > idx; i-- {
			if items[i].KlineInfo.Low.LessThan(low) {
				isSend = false
				continue
			}
		}
		if isSend {
			kind = "down"
			msg.Subtitle = fmt.Sprintf("当前: %s, 跌破%s低点", kline.KlineInfo.Close.String(), triggerTime.Format("01-02 15:04"))
		}
	}

	if msg.Subtitle != "" {
		// 判断是否已存在
		redisKey := fmt.Sprintf("stock:worker:60mcci:%s:%s:%s", symbol, triggerTime.Format("0102150405"), kind)
		if c.svcCtx.Redis.Exists(c.svcCtx.Ctx, redisKey).Val() == 1 {
			return
		}
		c.svcCtx.Logger.Info(c.svcCtx.Ctx, "message", zap.String("title", msg.Title), zap.String("msg", msg.Subtitle))
		_ = c.svcCtx.Nats.Publish(consts.TopicMessage, msg.Marshal())
		// 设置已发送
		c.svcCtx.Redis.Set(c.svcCtx.Ctx, redisKey, fmt.Sprintf("%s %s", msg.Title, msg.Subtitle), 7*24*time.Hour) // 一周
	}
}
